package se

import (
	"bytes"
	"os"
	"regexp"
	"strconv"
	"strings"

	"gitee.com/xuender/oils/level"
	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/sets"
	"gitee.com/xuender/oils/tags"
	"gitee.com/xuender/oils/u32"
	"gitee.com/xuender/se/pb"
)

// Service 搜索引擎服务.
type Service struct {
	ctx       *Context
	ls        *level.Service
	fs        *FileService
	ws        *WordService
	configKey [2]byte // 配置文件
	config    *pb.Config
}

// NewService 新建搜索引擎服务.
func NewService(
	ctx *Context,
	ls *level.Service,
	fs *FileService,
	ws *WordService,
) *Service {
	const (
		width  uint32 = 1000
		height uint32 = 800
	)

	configKey := [2]byte{ByteConfig, ByteConfig}
	config := &pb.Config{
		DataBasePath: ctx.DataBasePath,
		Exts:         []string{".go", ".ts", ".js", ".java", ".txt"},
		Ignore:       []string{"vendor", "target", "node_modules", "e2e", "typings"},
		Width:        width,
		Height:       height,
	}
	_ = ls.Get(configKey[:], config)

	return &Service{
		ctx:       ctx,
		ls:        ls,
		fs:        fs,
		ws:        ws,
		configKey: configKey,
		config:    config,
	}
}

// SetConfig 保存配置.
func (p *Service) SetConfig(config *pb.Config) {
	p.config = config

	p.ls.Put(p.configKey[:], config)
}

// Close 关闭.
func (p *Service) Close() error {
	return p.ls.DB.Close()
}

// FileInfo 文件信息.
func (p *Service) FileInfo(file string) *pb.File {
	var fi *pb.File

	if strings.Contains(file, string(os.PathSeparator)) {
		var change bool
		fi, change = p.fs.File(file)

		if change {
			panic(ErrFileChange)
		}
	}

	if fi == nil {
		i := logs.PanicInt(strconv.Atoi(file))
		_, fi = p.fs.fileByID(uint32(i))
	}

	if fi != nil {
		fk := p.fs.fileWordPrefix[:]
		fk = append(fk, u32.Bytes(fi.Id)...)
		ts := tags.NewTags()
		// 文件包含的词数量
		if p.ls.Get(fk, &ts) {
			fi.IndexSize = uint32(len(ts))
			fi.WordCount = uint32(ts.Count())
			wis := p.ws.Words(ts.Slice())
			fi.Words = make([]string, len(wis))

			for i, w := range wis {
				fi.Words[i] = w.Word
			}
		}

		return fi
	}

	return &pb.File{Name: file}
}

// WordList 词列表.
func (p *Service) WordList(start string) []*pb.Word {
	ids := []uint32{}
	keys := [][]byte{}

	startKey := []byte{}
	if start != "" {
		startKey = append(startKey, p.ws.wordPrefix[:]...)
		startKey = append(startKey, []byte(start)...)
	}

	_ = p.ls.QueryAndKey(p.ws.wordPrefix[:], int(p.ctx.Limit), &ids, startKey, &keys)
	words := make([]*pb.Word, len(ids))

	for i, id := range ids {
		tag := tags.Tags{}
		ik := p.fs.wordFilePrefix[:]
		ik = append(ik, u32.Bytes(id)...)

		p.ls.Get(ik, &tag)

		words[i] = &pb.Word{
			Id:    id,
			Word:  string(keys[i][2:]),
			Count: uint32(tag.Count()),
		}
	}

	return words
}

// WordInfo 词信息.
func (p *Service) WordInfo(word string) *pb.Word {
	var wid uint32
	if id, err := strconv.Atoi(word); err == nil {
		wid = uint32(id)
	} else {
		wid = p.ws.Word(word, false)
		if wid == 0 {
			return &pb.Word{Word: word}
		}
	}

	wids := []uint32{wid}
	ws := p.ws.Words(wids)

	if len(ws) > 0 {
		wi := ws[0]
		fs := p.fs.FileByWord(wids)
		wi.Count = uint32(len(fs))
		wi.Files = make([]*pb.File, len(fs))
		wk := p.fs.wordFilePrefix[:]
		wk = append(wk, u32.Bytes(wid)...)
		tag := tags.NewTags()

		if p.ls.Get(wk, &tag) {
			wi.IndexSize = uint32(len(tag))
		}

		for i, f := range fs {
			fk := p.fs.fileIDPrefix[:]

			fk = append(fk, u32.Bytes(f)...)
			fi := &pb.File{}
			_ = p.ls.Get(fk, fi)
			wi.Files[i] = fi
		}

		return wi
	}

	return &pb.Word{Word: word}
}

// FindWords 根据文件ID和词组，查找包含词ID.
func (p *Service) FindWords(fileIDs []uint32, words string) []uint32 {
	files := p.fs.Files(fileIDs)
	hasFileIDs := []uint32{}
	ws := WordsMay(words)

	for _, f := range files {
		if f.IsDir {
			continue
		}

		bs := logs.PanicBytes(os.ReadFile(f.Name))
		str := strings.ToLower(string(bytes.ReplaceAll(bs, []byte{'_'}, []byte{})))

		for _, w := range ws {
			if strings.Contains(str, w) {
				hasFileIDs = append(hasFileIDs, f.Id)

				continue
			}
		}
	}

	wordIDs := []uint32{}
	// 词组命中
	if len(hasFileIDs) > 0 {
		id := p.ws.Word(words, true)
		tag := tags.NewTags()

		for _, fid := range hasFileIDs {
			fk := p.fs.fileWordPrefix[:]
			fk = append(fk, u32.Bytes(fid)...)
			ts := tags.NewTags()
			_ = p.ls.Get(fk, &ts)

			ts.Add(id)
			p.ls.Put(fk, &ts)
			tag.Add(fid)
		}

		wk := p.fs.wordFilePrefix[:]
		wk = append(wk, u32.Bytes(id)...)

		p.ls.Put(wk, &tag)
		p.ws.words.Add(words)
		p.ls.Put(p.ws.wordsKey[:], p.ws.words)

		wordIDs = append(wordIDs, id)
	}

	return wordIDs
}

// WordsMay 词组的可能存在形式.
func WordsMay(words string) []string {
	re := regexp.MustCompile(`\w+|\p{Han}+`)
	ws := re.FindAllString(words, -1)
	set := sets.Strings{}

	set.Add(
		strings.ToLower(words),
		strings.ToLower(strings.Join(ws, " ")),
		strings.ToLower(strings.Join(ws, "")),
	)

	return set.Slice()
}
